﻿using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Interface;

namespace Nvigorate.Test.Relational.Binding
{
    [TestClass]
    public class SourceMapBaseTests
    {
        #region Fields

        private const string ELEMENT = "<source name=\"tableA\" />";
        private const string ELEMENT_PERSIST_ORDER = "<source name=\"tableA\" persistOrder=\"2\" />";
        private const string ELEMENT_FORBIDDEN_SINGLE = "<source name=\"tableA\" forbidden=\"insert\" />";
        private const string ELEMENT_FORBIDDEN_MULTIPLE = "<source name=\"tableA\" forbidden=\"insert,update\" />";
        private const string ELEMENT_ASSIGNMENTS = "<source name=\"tableA\"><assignment /><assignment /></source>";
        private const string ELEMENT_JOIN = "<source name=\"tableA\"><relates  /></source>";
        private const string ELEMENT_SOURCE_TYPE_PARENT = "<source name=\"tableA\" type=\"Parent\" />";
        private const string ELEMENT_SOURCE_TYPE_MANY_TO_MANY = "<source name=\"tableA\" type=\"ManyToMany\" />";
        private const string ELEMENT_SOURCE_TYPE_CHILD = "<source name=\"tableA\" type=\"Child\" />";

        private const string ELEMENT_DEFAULT = "<source name=\"tableA\" persistOrder=\"0\" forbidden=\"\" type=\"Parent\" />";
        private const string ELEMENT_SET_PERSIST_ORDER = "<source name=\"tableA\" persistOrder=\"2\" forbidden=\"\" type=\"Parent\" />";

        private const string ELEMENT_SET_FORBIDDEN_SINGLE =
            "<source name=\"tableA\" persistOrder=\"0\" forbidden=\"insert\" type=\"Parent\" />";

        private const string ELEMENT_SET_FORBIDDEN_MULTIPLE =
            "<source name=\"tableA\" persistOrder=\"0\" forbidden=\"insert,update\" type=\"Parent\" />";

        private const string ELEMENT_SET_ASSIGNMENTS =
            "<source name=\"tableA\" persistOrder=\"0\" forbidden=\"\" type=\"Parent\"><assignment /><assignment /></source>";

        private const string ELEMENT_SET_JOIN =
            "<source name=\"tableA\" persistOrder=\"0\" forbidden=\"\" type=\"Parent\" ><relates /></source>";

        private const string ELEMENT_SET_SOURCE_TYPE =
            "<source name=\"tableA\" persistOrder=\"0\" forbidden=\"\" type=\"ManyToMany\" ></source>";
        #endregion

        #region ParseXElement Tests

        [TestMethod]
        public void ParseXElement_WithNoAttributesExceptName_AssignsNameAndLeavesDefaultValues()
        {
            // Act
            var actual = new ConcreteSourceMap("Employee");
            actual.ParseXElement(XElement.Parse(ELEMENT));

            // Assert
            Assert.AreEqual("tableA", actual.SourceName);
            Assert.IsTrue(actual.CanDelete);
            Assert.IsTrue(actual.CanInsert);
            Assert.IsTrue(actual.CanSelect);
            Assert.IsTrue(actual.CanUpdate);
            Assert.AreEqual(0, actual.Assignments.Count);
            Assert.IsNull(actual.Join);
        }

        [TestMethod]
        public void ParseXElement_PersistOrderProvided_PopulatesPersistOrder()
        {
            // Act
            var actual = new ConcreteSourceMap("Employee");
            actual.ParseXElement(XElement.Parse(ELEMENT_PERSIST_ORDER));

            // Assert
            Assert.AreEqual(2, actual.CreateOrder);
        }

        [TestMethod]
        public void ParseXElement_ForbiddenInsert_OnlySetsCanInsertToFalse()
        {
            // Act
            var actual = new ConcreteSourceMap("Employee");
            actual.ParseXElement(XElement.Parse(ELEMENT_FORBIDDEN_SINGLE));

            // Assert
            Assert.IsFalse(actual.CanInsert);
            Assert.IsTrue(actual.CanDelete);
            Assert.IsTrue(actual.CanSelect);
            Assert.IsTrue(actual.CanUpdate);
        }

        [TestMethod]
        public void ParseXElement_ForbiddenInsertAndUpdate_OnlySetsInsertAndUpdateToFalse()
        {
            // Act
            var actual = new ConcreteSourceMap("Employee");
            actual.ParseXElement(XElement.Parse(ELEMENT_FORBIDDEN_MULTIPLE));

            // Assert
            Assert.IsFalse(actual.CanInsert);
            Assert.IsFalse(actual.CanUpdate);
            Assert.IsTrue(actual.CanDelete);
            Assert.IsTrue(actual.CanSelect);
        }

        [TestMethod]
        public void ParseXElement_HasAssignments_AreAddedToCollection()
        {
            // Act
            var actual = new ConcreteSourceMap("Employee");
            actual.ParseXElement(XElement.Parse(ELEMENT_ASSIGNMENTS));

            // Assert
            Assert.AreEqual(2, actual.Assignments.Count);
        }

        [TestMethod]
        public void ParseXElement_HasRelation_AreAddedToCollection()
        {
            // Arrange
            var targetName = "Employee";
            var join = new LeftJoin {Subject = "tableA", Relative = "tableB"};
            var element = XElement.Parse(ELEMENT_JOIN);

            // Act
            var actual = new ConcreteSourceMap(targetName, join) {SourceName = targetName};
            actual.ParseXElement(element);

            // Assert
            Assert.AreEqual(join, actual.Join);
        }

        [TestMethod]
        public void ParseXElement_HasParentType_SetsSourceTypeProperty()
        {
            // Act
            var actual = new ConcreteSourceMap("Employee");
            actual.ParseXElement(XElement.Parse(ELEMENT_SOURCE_TYPE_PARENT));

            // Assert
            Assert.AreEqual(SourceMapType.Parent, actual.SourceType);
        }

        [TestMethod]
        public void ParseXElement_HasManyToManyType_SetsSourceTypeProperty()
        {
            // Act
            var actual = new ConcreteSourceMap("Employee");
            actual.ParseXElement(XElement.Parse(ELEMENT_SOURCE_TYPE_MANY_TO_MANY));

            // Assert
            Assert.AreEqual(SourceMapType.ManyToMany, actual.SourceType);
        }

        [TestMethod]
        public  void ParseXElement_HasChildType_SetsSourceTypeProperty()
        {
            // Act
            var actual = new ConcreteSourceMap("Employee");
            actual.ParseXElement(XElement.Parse(ELEMENT_SOURCE_TYPE_CHILD));

            // Assert
            Assert.AreEqual(SourceMapType.Child, actual.SourceType);
        }

        [TestMethod]
        public void ParseXElement_NoTypeAttribute_SetsSourceTypeToParent()
        {
            // Act
            var actual = new ConcreteSourceMap("Employee");
            actual.ParseXElement(XElement.Parse(ELEMENT));

            // Assert
            Assert.AreEqual(SourceMapType.Parent, actual.SourceType);
        }
        #endregion

        #region GetXElementRepresentation Tests

        [TestMethod]
        public void GetXElementRepresentation_UsingDefaultValues_GeneratesXElement()
        {
            // Arrange
            var targetName = "Employee";
            var expected = XElement.Parse(ELEMENT_DEFAULT);
            var subject = new ConcreteSourceMap(targetName) {SourceName = "tableA"};

            // Act
            var actual = subject.GetXElementRepresentation();

            // Assert
            Assert.AreEqual(expected.Value, actual.Value);
        }

        [TestMethod]
        public void GetXElementRepresentation_SetCreateOrder_GeneratesXElement()
        {
            // Arrange
            var targetName = "Employee";
            var expected = XElement.Parse(ELEMENT_SET_PERSIST_ORDER);
            var subject = new ConcreteSourceMap(targetName) {SourceName = "tableA", CreateOrder = 2};

            // Act
            var actual = subject.GetXElementRepresentation();

            // Assert
            Assert.AreEqual(expected.Value, actual.Value);
        }

        [TestMethod]
        public void GetXElementRepresentation_SetSingleForbiddenOperation_GeneratesXElement()
        {
            // Arrange
            var targetName = "Employee";
            var expected = XElement.Parse(ELEMENT_SET_FORBIDDEN_SINGLE);
            var subject = new ConcreteSourceMap(targetName) {SourceName = "tableA", CanInsert = false};

            // Act
            var actual = subject.GetXElementRepresentation();

            // Assert
            Assert.AreEqual(expected.Value, actual.Value);
        }

        [TestMethod]
        public void GetXElementRepresentation_SetMultipleForbiddenOperation_GeneratesXElement()
        {
            // Arrange
            var targetName = "Employee";
            var expected = XElement.Parse(ELEMENT_SET_FORBIDDEN_MULTIPLE);
            var subject = new ConcreteSourceMap(targetName)
                              {SourceName = "tableA", CanInsert = false, CanUpdate = false};

            // Act
            var actual = subject.GetXElementRepresentation();

            // Assert
            Assert.AreEqual(expected.Value, actual.Value);
        }

        [TestMethod]
        public void GetXElementRepresentation_SetAssignments_GeneratesXElement()
        {
            // Arrange
            var targetName = "Employee";
            var expected = XElement.Parse(ELEMENT_SET_ASSIGNMENTS);
            var subject = new ConcreteSourceMap(targetName)
                              {
                                  SourceName = "tableA",
                                  Assignments = {CreateAssignment(), CreateAssignment()}
                              };

            // Act
            var actual = subject.GetXElementRepresentation();

            // Assert
            Assert.AreEqual(expected.Value, actual.Value);
        }

        [TestMethod]
        public void GetXElementRepresentation_SetJoin_GeneratesXElement()
        {
            // Arrange
            var targetName = "Employee";
            var expected = XElement.Parse(ELEMENT_SET_JOIN);
            var join = new Mock<IJoin>();
            join.Expect(j => j.GetXElementRepresentation()).Returns(XElement.Parse("<relates />"));

            var subject = new ConcreteSourceMap(targetName)
                              {
                                  SourceName = "tableA",
                                  Join = join.Object
                              };

            // Act
            var actual = subject.GetXElementRepresentation();

            // Assert
            Assert.AreEqual(expected.Value, actual.Value);
        }

        [TestMethod]
        public void GetXElementRepresentation_SourceTypeIsManyToMany_GeneratesXElement()
        {
            // Arrange
            var targetName = "Employee";
            var expected = XElement.Parse(ELEMENT_SET_SOURCE_TYPE);
            var subject = new ConcreteSourceMap(targetName) { SourceName = "tableA", SourceType = SourceMapType.ManyToMany};

            // Act
            var actual = subject.GetXElementRepresentation();

            // Assert
            Assert.AreEqual(expected.Value, actual.Value);
        }
        #endregion

        #region Private Methods

        private static IAssignment CreateAssignment()
        {
            var assignment = new Mock<IAssignment>();
            assignment.Expect(a => a.GetXElementRepresentation()).Returns(XElement.Parse("<assignment />"));
            return assignment.Object;
        }

        #endregion

        #region Nested Types

        private class ConcreteSourceMap : SourceMapBase
        {
            private readonly IJoin _expectedJoin;

            public ConcreteSourceMap(string targetName) : base(targetName)
            {
                SourceType = SourceMapType.Parent;
            }

            public ConcreteSourceMap(string targetName, IJoin expectedJoin) : this(targetName)
            {
                _expectedJoin = expectedJoin;
            }

            public override bool Equals(object obj)
            {
                return base.Equals((SourceMapBase) obj);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }

            protected override IAssignment ParseAssignment(XElement element)
            {
                return CreateAssignment();
            }

            protected override IJoin ParseJoin(XElement element)
            {
                return _expectedJoin;
            }
        }

        #endregion
    }
}